
package com.sausageking.server;

import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.common.base.Strings;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.googlecode.objectify.NotFoundException;
import com.sausageking.client.ConsumerService;
import com.sausageking.server.common.ServerUtil;
import com.sausageking.server.dao.GenericDao;
import com.sausageking.shared.BaseUtil;
import com.sausageking.shared.BuyerException;
import com.sausageking.shared.BuyerException.Code;
import com.sausageking.shared.Checkin;
import com.sausageking.shared.Consumer;
import com.sausageking.shared.Merchant;
import com.sausageking.shared.Redemption;

public class ConsumerServiceImpl extends RemoteServiceServlet
    implements
      ConsumerService {

  private static final long serialVersionUID = 8031559183199386651L;

  private static final Logger log = Logger.getLogger(ConsumerServiceImpl.class
      .getName());

  private AuthManager authManager = new AuthManagerImpl();

  private BaseUtil util = new ServerUtil();

  @Override
  public Checkin stamp(String email, String merchantId, String merchantToken)
      throws BuyerException {
    email = email.trim().toLowerCase();
    // verify token
    GenericDao<Merchant> dao = new GenericDao<Merchant>(Merchant.class);
    Merchant merchant;
    try {
      merchant = dao.get(Long.valueOf(merchantId));
    } catch (NumberFormatException e) {
      throw new BuyerException(Code.MERCHANT_NOT_FOUND);
    } catch (EntityNotFoundException e) {
      throw new BuyerException(Code.MERCHANT_NOT_FOUND);
    } catch (NotFoundException e) {
      throw new BuyerException(Code.MERCHANT_NOT_FOUND);
    }
    new MerchantTokenManagerImpl().validate(merchant, merchantToken);

    // check last checkin time

    try {
      MerchantCustomerInfo info = getMerchantCustomerInfo(email, merchantId);
      if (checkinTooOften(info)) {
        throw new BuyerException(Code.STAMP_TOO_OFTEN);
      }
    } catch (BuyerException e) {
      if (!e.getCode().equals(Code.UNKNOWN_USER)) {
        throw e;
      }
    }

    // checkin
    Checkin checkin = new Checkin();
    checkin.setMerchantId(merchantId);
    checkin.setUserId(email);
    checkin.setCreationTime(new Date());
    GenericDao<Checkin> checkinDao = new GenericDao<Checkin>(Checkin.class);
    checkinDao.put(checkin);

    // create user if it doesn't exist
    GenericDao<Consumer> userDao = new GenericDao<Consumer>(Consumer.class);
    try {
      userDao.get(email);
    } catch (NotFoundException e) {
      userDao.put(new Consumer().setEmail(email));
      log.info("created user at stamping: " + email);
    }

    log.info(checkin.toString());
    return checkin;
  }

  private boolean checkinTooOften(MerchantCustomerInfo info) {
    List<Checkin> checkins = info.getCheckins();
    if (checkins == null || checkins.isEmpty()) {
      return false;
    }

    if (util.isTestAccount(info.getUser().getEmail())) {
      return false;
    }

    Checkin lastCheckin = checkins.get(0);
    DateTimeZone timeZone = DateTimeZone.forID(info.getMerchant()
        .getTimeZoneId());
    DateTime lastCheckinMidNight = new DateTime(lastCheckin.getCreationTime()
        .getTime(), timeZone).toDateMidnight().toDateTime();
    DateTime nowMidNight = new DateTime(timeZone).toDateMidnight().toDateTime();
    log.info("now mid:" + nowMidNight.toString() + ", last mid: "
        + lastCheckinMidNight.toString());
    return !lastCheckinMidNight.isBefore(nowMidNight);
  }

  @Override
  public MerchantCustomerInfo getMerchantCustomerInfo(String email,
      String merchantId) throws BuyerException {
    MerchantCustomerInfo result = new MerchantCustomerInfo();
    GenericDao<Merchant> dao = new GenericDao<Merchant>(Merchant.class);
    Merchant merchant;
    try {
      merchant = dao.get(Long.valueOf(merchantId));
    } catch (NumberFormatException e) {
      throw new BuyerException(Code.MERCHANT_NOT_FOUND);
    } catch (EntityNotFoundException e) {
      throw new BuyerException(Code.MERCHANT_NOT_FOUND);
    } catch (NotFoundException e) {
      throw new BuyerException(Code.MERCHANT_NOT_FOUND);
    }
    merchant.setSalt(null);
    result.setMerchant(merchant);

    if (!Strings.isNullOrEmpty(email)) {
      // user
      GenericDao<Consumer> userDao = new GenericDao<Consumer>(Consumer.class);
      try {
        Consumer user = userDao.get(email);
        if (user.getPassword() != null) {
          user.setPassword("PASSWORDPROTECTED");
        }
        result.setUser(user);
      } catch (NotFoundException e) {
        throw new BuyerException(Code.UNKNOWN_USER);
      }

      // redemptions
      GenericDao<Redemption> redemptionDao = new GenericDao<Redemption>(
          Redemption.class);
      List<Redemption> redemptions = redemptionDao
          .listByExample(new Redemption().setMerchantId(merchantId).setEmail(
              email));
      log.info("#redemptions:" + redemptions.size());
      Collections.sort(redemptions, new Comparator<Redemption>() {

        @Override
        public int compare(Redemption c1, Redemption c2) {
          return c2.getCreationTime().compareTo(c1.getCreationTime());
        }
      });
      result.setRedemptions(redemptions);

      // checkins
      List<Checkin> checkins = loadCheckins(merchantId, email);
      result.setCheckins(checkins);
    }

    log.info(result.toString());
    return result;
  }

  private List<Checkin> loadCheckins(String merchantId, String email) {
    GenericDao<Checkin> checkinDao = new GenericDao<Checkin>(Checkin.class);

    List<Checkin> checkins = checkinDao.listByExample(new Checkin()
        .setMerchantId(merchantId).setUserId(email));
    Collections.sort(checkins, new Comparator<Checkin>() {

      @Override
      public int compare(Checkin c1, Checkin c2) {
        return c2.getCreationTime().compareTo(c1.getCreationTime());
      }
    });
    return checkins;
  }

  @Override
  public RedeemResponse redeem(String email, String merchantId,
      AuthType authType, String secret) throws BuyerException {
    email = email.trim().toLowerCase();
    secret = secret.trim();
    if (authType.equals(AuthType.PASSWORD)) { // password-based auth
      if (authManager.hasPassword(email)) { // user has set password
        if (!authManager.authenticate(AuthType.PASSWORD, email, secret)) {
          throw new BuyerException(Code.AUTH_FAILED);
        }
      } else { // user hasn't set password
        try {
          setPassword(email, secret);
        } catch (NotFoundException e) {
          throw new BuyerException(Code.UNKNOWN_USER);
        } catch (EntityNotFoundException e) {
          throw new BuyerException(Code.UNKNOWN_USER);
        }
      }
    } else {// session token based auth
      if (!authManager.authenticate(AuthType.TOKEN, email, secret)) {
        throw new BuyerException(Code.AUTH_FAILED);
      }
    }

    Redemption redemption = new Redemption().setMerchantId(merchantId)
        .setEmail(email).setCreationTime(new Date());
    GenericDao<Redemption> redemptionDao = new GenericDao<Redemption>(
        Redemption.class);
    redemptionDao.put(redemption);
    String token = null;
    if (authType.equals(AuthType.PASSWORD)) {
      token = ""; // TODO: get access token
    }
    return new RedeemResponse(redemption, token);
  }

  private void setPassword(String email, String secret)
      throws EntityNotFoundException, NotFoundException {
    GenericDao<Consumer> userDao = new GenericDao<Consumer>(Consumer.class);
    Consumer user = userDao.get(email);
    user.setPassword(authManager.hashPassword(secret));
    userDao.put(user);
  }

  @Override
  public String signIn(String email, String password) throws BuyerException {
    if (!authManager.authenticate(AuthType.PASSWORD, email, password)) {
      throw new BuyerException(Code.AUTH_FAILED);
    }
    String token = "token"; // TODO: get access token
    log.info("email: " + email + ", sessionId:" + token);
    return token;
  }

  @Override
  public String signUp(String email, String password) throws BuyerException {
    GenericDao<Consumer> dao = new GenericDao<Consumer>(Consumer.class);

    try {
      dao.get(email);
      throw new BuyerException(Code.USER_ALREADY_EXISTS);
    } catch (NotFoundException e) {

    }

    Consumer user = new Consumer();
    user.setEmail(email).setPassword(authManager.hashPassword(password));
    dao.put(user);
    return "token"; // TODO: get access token
  }
}